#include "AutoWall.h"
#include "../GameTrace/GameTrace.h"
#include "../SDK/Global.hpp"
#include <string>
#include "../IEngineCvar/IEngineCvar.hpp"
#include "../cs2 dumper/offsets.hpp"
#include "../cs2 dumper/client_dll.hpp"

#define GetField(pBase, Class, Field, Type) *reinterpret_cast<Type*>(static_cast<uintptr_t>(pBase) + cs2_dumper::schemas::client_dll::Class::Field);

static bool IsPlayer(uintptr_t PawnBase)
{
	if (!PawnBase) {
		return false;
	}
	std::string Name = Global::GetEntityName(PawnBase);
	return (Name.find("c_cs_player_for_precache") != std::string::npos);
}

static float ScaleDamage(const int nHitgroup, void* pTarget, void* pWeaponData, float& flDamage)
{
	if (!pTarget || !pWeaponData) {
		return flDamage;
	}

	const int nTeamNum = GetField((uintptr_t)pTarget, C_BaseEntity, m_iTeamNum, uint8_t);

	const float flTerroristHeadDamageScale = IEngineCvar::Get()->Find("mp_damage_scale_t_head")->Value.Float;
	const float flTerroristBodyDamageScale = IEngineCvar::Get()->Find("mp_damage_scale_t_body")->Value.Float;
	const float flCounterTerroristHeadDamageScale = IEngineCvar::Get()->Find("mp_damage_scale_ct_head")->Value.Float;
	const float flCounterTerroristBodyDamageScale = IEngineCvar::Get()->Find("mp_damage_scale_ct_body")->Value.Float;

	const float flHeadDamageScale = (nTeamNum == 3) ? flCounterTerroristHeadDamageScale : (nTeamNum == 2) ? flTerroristHeadDamageScale : 1.f;
	const float flBodyDamageScale = (nTeamNum == 3) ? flCounterTerroristBodyDamageScale : (nTeamNum == 2) ? flTerroristBodyDamageScale : 1.f;

	enum EHitgroups : int
	{
		HITGROUP_INVALID = -1,
		HITGROUP_GENERIC = 0,
		HITGROUP_HEAD,
		HITGROUP_CHEST,
		HITGROUP_STOMACH,
		HITGROUP_LEFTARM,
		HITGROUP_RIGHTARM,
		HITGROUP_LEFTLEG,
		HITGROUP_RIGHTLEG,
		HITGROUP_NECK,
		HITGROUP_UNUSED,
		HITGROUP_GEAR,
		HITGROUP_SPECIAL,
		HITGROUP_COUNT,
	};

	float flHeadshotMult = GetField((uintptr_t)pWeaponData, CCSWeaponBaseVData, m_flHeadshotMultiplier, float);

	switch (nHitgroup)
	{
	case HITGROUP_HEAD:
		flDamage *= flHeadshotMult * flHeadDamageScale;
		break;
	case HITGROUP_NECK:
	case HITGROUP_CHEST:
	case HITGROUP_LEFTARM:
	case HITGROUP_RIGHTARM:
		flDamage *= flBodyDamageScale;
		break;
	case HITGROUP_STOMACH:
		flDamage *= 1.25f * flBodyDamageScale;
		break;
	case HITGROUP_LEFTLEG:
	case HITGROUP_RIGHTLEG:
		flDamage *= 0.75f * flBodyDamageScale;
		break;
	default:
		break;
	}

	int ArmorValue = GetField((uintptr_t)pWeaponData, C_CSPlayerPawn, m_ArmorValue, int);

	if (nHitgroup == HITGROUP_HEAD)
	{
		uintptr_t pItemServices = GetField((uintptr_t)pWeaponData, C_BasePlayerPawn, m_pItemServices, uintptr_t);
		if (pItemServices) {
			bool bHasHelmet = GetField((uintptr_t)pWeaponData, CCSPlayer_ItemServices, m_bHasHelmet, bool);
			if (!bHasHelmet) {
				return flDamage;
			}
		}

	}
	else if (ArmorValue <= 0) {
		return flDamage;
	}

	float flArmorBonus = 0.5f;
	float flHeavyArmorBonus = 1.f;
	float m_iflArmorRatio = GetField((uintptr_t)pWeaponData, CCSWeaponBaseVData, m_flArmorRatio, float);
	float flArmorRatio = m_iflArmorRatio * 0.5f;
	uintptr_t pItemServices = GetField((uintptr_t)pWeaponData, C_BasePlayerPawn, m_pItemServices, uintptr_t);
	if (pItemServices) {
		bool bHasHeavyArmor = GetField((uintptr_t)pWeaponData, CCSPlayer_ItemServices, m_bHasHeavyArmor, bool);
		if (bHasHeavyArmor) {
			flArmorBonus = 0.33f;
			flHeavyArmorBonus = 0.25f;
			flArmorRatio = flArmorRatio * 0.2f;
		}
	}

	float flDamageToHealth = flDamage * flArmorRatio;
	const float flArmorValue = static_cast<float>(ArmorValue);
	const float flDamageToArmor = (flDamage - flDamageToHealth) * (flHeavyArmorBonus * flArmorBonus);
	if (flDamageToArmor > flArmorValue) {
		flDamageToHealth = flDamage - flArmorValue / flArmorRatio;
	}

	flDamage = flDamageToHealth;
	return flDamage;
}

bool AutoWall::FireBullet(float MinDamage, void* LocalPawn, void* WeaponData, Vector3 vecFrom, Vector3 vecTarget, void* pTarget, float& Damage)
{
	float flTraceLength = 0.f;
	CTraceFilter pFilter = {};
	CTraceData pTraceData = {};
	pTraceData.pArray = &pTraceData.arrTrace;

	Ray::InitFilter(&pFilter, (void*)LocalPawn, 0x1C300B, 4, 7);

	float flWeaponDamage = (float)GetField((uintptr_t)WeaponData, CCSWeaponBaseVData, m_nDamage, int);
	const float flRange = GetField((uintptr_t)WeaponData, CCSWeaponBaseVData, m_flRange, float);
	const float flPenetration = GetField((uintptr_t)WeaponData, CCSWeaponBaseVData, m_flPenetration, float);
	const float flRangeModifier = GetField((uintptr_t)WeaponData, CCSWeaponBaseVData, m_flRangeModifier, float);

	//__int64 __fastcall sub_1805A2AE0(__int64 a1, __int64 a2, __int64 a3, float a4, char a5,
	static auto fnInitTraceData = reinterpret_cast<void(__fastcall*)(CTraceData*, Vector3, Vector3, CTraceFilter, int)>(client.dll + 0x5A0430);

	// sub_1811F6010(&v230);
	// v63 = *(unsigned int*)(v62 + 4);
	static auto fnInitGameTrace = reinterpret_cast<void(__fastcall*)(CGameTrace*)>(client.dll + 0x11F6010);
	// sub_1805A1010(&v282, &v230, v64, &v283[48 * *(unsigned __int16*)(v62 + 18)], v187, v189, v191);
	static auto fnGetTraceInfo = reinterpret_cast<void(__fastcall*)(CTraceData*, CGameTrace*, float, CTraceArrayElement*)>(client.dll + 0x5A1010);
	//  LODWORD(v189) = sub_18071B540(v28);
	//  sub_1805A0EF0((unsigned __int64)&v282, v43, v44, v45, v39);
	//while (1)
	//{
	//	v13 = v12 + *(_QWORD*)(v8 + 6184);
	//	result = sub_1805B2D30(v8, &v17, v12 + *(_QWORD*)(v8 + 6184), a7, a8);
	static auto fnHandleBulletPenetration = reinterpret_cast<bool(__fastcall*)(CTraceData*, CHandleBulletData*, CTraceInfo*, int, void*)>(client.dll + 0x5B2D30);
	
	fnInitTraceData(&pTraceData, vecFrom, vecTarget - vecFrom, pFilter, 4);

	if (pTraceData.nPenetrationCount > 0 && pTraceData.pTraceInfo)
	{
		float flMaxRange = flRange;

		CHandleBulletData pHandleBulletData(flWeaponDamage, flPenetration, flRangeModifier, flRange, 4, false);

		for (int nIndex = 0; nIndex < pTraceData.nPenetrationCount; nIndex++)
		{
			CTraceInfo* pTraceInfo = pTraceData.pTraceInfo + nIndex;

			if (!pTraceInfo) {
				continue;
			}

			CGameTrace pGameTrace = {};
			fnInitGameTrace(&pGameTrace);

			const int nEntryIndex = pTraceInfo->hEntity & 0x7FFF;

			fnGetTraceInfo(
				&pTraceData,
				&pGameTrace,
				0.f,
				reinterpret_cast<CTraceArrayElement*>(reinterpret_cast<uintptr_t>(pTraceData.arrTrace.data()) + nEntryIndex * sizeof(CTraceArrayElement))
			);

			flMaxRange -= flTraceLength;
			flTraceLength += (pGameTrace.flFraction * flMaxRange);

			flWeaponDamage *= std::powf(flRangeModifier, flTraceLength / 500.f);

			if (flTraceLength > flRange || pGameTrace.flFraction >= 1.f) {

				break;
			}

			if (pGameTrace.pHitEntity && IsPlayer((uintptr_t)pGameTrace.pHitEntity))
			{
				if (flWeaponDamage < 1.f) {
					continue;
				}

				if (pTarget && pGameTrace.pHitEntity != pTarget) {
					break;
				}

				pTraceData.Release();
				ScaleDamage(pGameTrace.GetHitgroup(), pGameTrace.pHitEntity, WeaponData, flWeaponDamage);
				Damage = flWeaponDamage;
				return flWeaponDamage > MinDamage;
			}
			uint8_t Local_TeamNum = GetField((uintptr_t)LocalPawn, C_BaseEntity, m_iTeamNum, uint8_t);
			if (fnHandleBulletPenetration(&pTraceData, &pHandleBulletData, pTraceInfo, Local_TeamNum, nullptr))
			{
				pTraceData.Release();
				return false;
			}
			Damage = flWeaponDamage = pHandleBulletData.flDamage;
		}
	}
	pTraceData.Release();
	return false;
}